package block

import (
	"bytes"
	"fmt"
	"github.com/boltdb/bolt"
	"publicChainSystemTest/transaction"
)

/**
 *@author:zhengyilong
 *@email:1797344574@qq.com
 *@phone:13479394730
 *@DateTime:2022/4/11 9:22
 **/

const (
	CHAIN_DB_PATH = "./chain.db" 	//数据库地址
	BUCKET_BLOCK = "chain_blocks"	//桶1名字
	BUCKET_STATUS = "chain_status"	//桶2名字
	LAST_HASH = "last_hash"			//桶2	key值
)

type BlockChain struct {
	//Blocks []*Block
	Bolt *bolt.DB
	LastHash []byte
}

func NewBlockChain(address string) (*BlockChain, error) {
	open, err := bolt.Open(CHAIN_DB_PATH, 0600, nil)
	if err!=nil {
		return nil, err
	}

	var lastHash []byte

	err = open.Update(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(BUCKET_BLOCK))
		if bucket == nil {
			coinbase, err := transaction.NewCoinbase(address)
			if err != nil {
				return err
			}

			createBucket, err := tx.CreateBucket([]byte(BUCKET_BLOCK))
			if err != nil {
				return err
			}

			block := NewGenesisBlock(*coinbase)
			serialize, err := block.Serialize()
			if err != nil {
				return err
			}

			err = createBucket.Put(block.Hash, serialize)
			if err != nil {
				return err
			}

			createBucket2, err := tx.CreateBucket([]byte(BUCKET_STATUS))
			if err != nil {
				return err
			}

			err = createBucket2.Put([]byte(LAST_HASH), block.Hash)
			if err != nil {
				return err
			}

			lastHash = block.Hash
		} else {
			b := tx.Bucket([]byte(BUCKET_STATUS))
			get := b.Get([]byte(LAST_HASH))
			lastHash = get
		}
		return nil
	})

	chain := BlockChain{
		Bolt:     open,
		LastHash: lastHash,
	}

	return &chain,err
}

func (bc *BlockChain)Addblock(tx []transaction.Transaction)(error) {
	//prevblock := bc.Blocks[len(bc.Blocks)-1]
	newBlock := NewBlock(tx, bc.LastHash)
	serialize, err := newBlock.Serialize()
	if err != nil {
		return err
	}
	//bc.Blocks = append(bc.Blocks, newBlock)
	err = bc.Bolt.Update(func(tx *bolt.Tx) error {
		bk := tx.Bucket([]byte(BUCKET_BLOCK))
		bk.Put(newBlock.Hash,serialize)

		bk2 := tx.Bucket([]byte(BUCKET_STATUS))
		bk2.Put([]byte(LAST_HASH),newBlock.Hash)
		bc.LastHash = newBlock.Hash
		return nil
	})
	return err
}

//创建一个迭代器对象,迭代器只能在有区块链的情况下才可以使用迭代器
func (bc *BlockChain) Iterator() *ChainIterator {
	iterator := ChainIterator{
		DB:          bc.Bolt,
		CurrentHash: bc.LastHash,
	}
	return &iterator
}

//获取所有区块
func (bc *BlockChain) GetAllBlock() ([]*Block, error) {
	var blockChain []*Block

	iterator := bc.Iterator()
	for{
		if iterator.HashNext() {
			block, err := iterator.Next()
			if err != nil {
				return nil, err
			}
			blockChain = append(blockChain, block)
		}else {
			break
		}
	}
	return blockChain,nil
}

//定义一个方法，用来找某个地址的所有收入
func (bc *BlockChain) FindAllOutput(address string) []transaction.UTXO {
	blocks, err := bc.GetAllBlock()
	if err!=nil {
		fmt.Println("寻找失败",err.Error())
		return nil
	}

	allOutputs := make([]transaction.UTXO,0)

	for _, block := range blocks {
		for _, tx := range block.Txs {
			for outIndex, output := range tx.Output {
				if output.IsUnLock(address) {
					utxo := transaction.NewUTXO(tx.TXid, outIndex, &output)
					allOutputs = append(allOutputs,utxo)
				}
			}
		}
	}
	return allOutputs
}

//寻找某个人的所有信息（input）
func (bc *BlockChain) FindAllInput(address string) []transaction.Input {
	allInputs := make([]transaction.Input,0)
	blocks, err := bc.GetAllBlock()
	if err != nil {
		fmt.Println("寻找失败",err.Error())
		return nil
	}

	for _, block := range blocks {
		for _,tx := range block.Txs{
			for _, input := range tx.Input {
				if input.Islocked(address) {
					allInputs = append(allInputs,input)
				}
			}
		}
	}
	return allInputs
}

//接收某个人的所有的收入和消费，并从所有的收入中去掉消费，剩下的就是可用的交易输出
func (bc *BlockChain) FindSpendOutputs(alloutputs []transaction.UTXO, allinputs []transaction.Input,amout uint) []transaction.UTXO {
	for _, input := range allinputs {
		for index, utxo := range alloutputs {
			if bytes.Compare(utxo.Txid, input.TXid) == 0 || utxo.Index == input.Vout {
				alloutputs = append(alloutputs[:index],alloutputs[index+1:]...)
			}
		}
	}

	var totalAmount uint
	outputs := make([]transaction.UTXO,0)
	for _, output := range alloutputs {
		totalAmount += output.Value
		outputs = append(outputs,output)
		if totalAmount >= amout {
			break
		}
	}
	return outputs
}